package com.me.common.util.io;

import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author ME
 * @date 2020/11/19
 */
public class HttpUtils {
    // 单例 HttpClient 实例
    private static final CloseableHttpClient httpClient;

    /** 建立 socket连接 的超时 5s */
    private static final int defaultConnectTimeout = 5000;
    /** 读数据超时时间：从服务器获取响应数据的超时时间，默认 30s */
    private static final int defaultSocketTimeout = 30000;

    static {
        // 1. 初始化连接池（保持默认配置）
        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
        connManager.setMaxTotal(100); // 全局最大连接数
        connManager.setDefaultMaxPerRoute(100); // 默认单域名并发数

        // 2. 为 www.baidu.com 设置更高的并发限制
        connManager.setMaxPerRoute(new HttpRoute(new HttpHost("www.baidu.com", -1, "https"), null, true), 64);

        // 创建 HttpClient
        httpClient = HttpClients.custom()
                .setConnectionManager(connManager)
                .setDefaultRequestConfig(defaultTimeOutConfig())
                // .setMaxConnPerRoute(100)
                .evictIdleConnections(30, TimeUnit.SECONDS) // 空闲连接的回收时间
                .build();
    }

    public static HttpResponse doGet(String path,
                                     Map<String, String> headers,
                                     Map<String, String> queryMap) throws Exception {
        HttpGet request = new HttpGet(buildUrl(path, queryMap));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(defaultConnectTimeout)
                .setSocketTimeout(defaultSocketTimeout)
                .build();
        request.setConfig(requestConfig);
        return httpClient.execute(request);
    }

    public static HttpResponse doPost(String path, Map<String, String> headers, Map<String, String> query,
                                      String body) throws Exception {
        return doPost(path, headers, query, body, defaultConnectTimeout, defaultSocketTimeout);
    }

    /**
     * post
     *
     * @param path
     * @param headers
     * @param query
     * @param body
     * @param connectionTimeout     建立连接的时间
     * @param socketTimeout         读取数据的时间
     * @return
     * @throws Exception
     */
    public static HttpResponse doPost(String path, Map<String, String> headers, Map<String, String> query,
                                      String body, int connectionTimeout, int socketTimeout) throws Exception {
        HttpPost request = new HttpPost(buildUrl(path, query));
        RequestConfig requestConfig = RequestConfig.custom()
                // 与服务器连接超时时间：httpClient 会创建一个异步线程用以创建socket连接，此处设置该socket的连接超时时间
                .setConnectTimeout(connectionTimeout > 0 ? connectionTimeout : defaultConnectTimeout)
                // socket 读数据超时时间：从服务器获取响应数据的超时时间
                .setSocketTimeout(socketTimeout > 0 ? socketTimeout : defaultSocketTimeout).build();
        request.setConfig(requestConfig);

        // 建立一个NameValuePair数组，用于存储欲传送的参数
        request.addHeader("Content-type", "application/json; charset=UTF-8");
        request.addHeader("Accept", "application/json");
        if (MapUtils.isNotEmpty(headers)) {
            for (Map.Entry<String, String> e : headers.entrySet()) {
                request.setHeader(e.getKey(), e.getValue());
            }
        }

        if (StringUtils.isNotBlank(body)) {
            request.setEntity(new StringEntity(body, StandardCharsets.UTF_8));
        }

        return httpClient.execute(request);
    }

    private static String buildUrl(String path, Map<String, String> querys) throws UnsupportedEncodingException {
        StringBuilder sbUrl = new StringBuilder();
        sbUrl.append(path);
        if (MapUtils.isNotEmpty(querys)) {
            StringBuilder sbQuery = new StringBuilder();
            for (Map.Entry<String, String> query : querys.entrySet()) {
                if (0 < sbQuery.length()) {
                    sbQuery.append("&");
                }
                if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
                    sbQuery.append(query.getValue());
                }
                if (!StringUtils.isBlank(query.getKey())) {
                    sbQuery.append(query.getKey());
                    if (!StringUtils.isBlank(query.getValue())) {
                        sbQuery.append("=");
                        sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
                    }
                }
            }
            if (0 < sbQuery.length()) {
                sbUrl.append("?").append(sbQuery);
            }
        }

        return sbUrl.toString();
    }

    public static void main(String[] args) throws Exception {
        HttpPost request = new HttpPost(buildUrl("https://www.baidu.com", null));

        HttpResponse response = HttpClients.createDefault().execute(request);
        System.out.println(EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8));
    }

    /**
     * 设置 连接超时、 请求超时 、 读取超时  毫秒
     *
     * @return
     */
    private static RequestConfig defaultTimeOutConfig() {
        return RequestConfig.copy(RequestConfig.DEFAULT)
                .setConnectionRequestTimeout(60000)// 从连接池中获取连接的超时时间
                .setConnectTimeout(5000) // 建立连接超时 5s
                .setSocketTimeout(90000)  // 从服务器读取数据的超时时间 5s
                .build();
    }

}
